package com.codeyang.mocktest2.mockit;

import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.Mockito;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.junit.MockitoJUnitRunner;
import org.mockito.stubbing.Answer;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;

import static org.mockito.Mockito.mock;

/**
 * 描述: describe
 *
 * @author shengyang5
 * @version 2021/7/22 13:59
 */
@RunWith(MockitoJUnitRunner.class)
public class MockitStudy02 {

    /**
     * 6 匹配任意参数
     *
     * @throws Exception
     */
    @Test
    public void test() throws Exception {
        //1模拟对象
        List list = mock(List.class);
        //2匹配任意参数
        Mockito.when(list.get(1)).thenReturn(1);
        // Mockito.argThat(new IsValid())) 使用自己的参数匹配器 自定义匹配规则
        Mockito.when(list.contains(Mockito.argThat(new IsValid()))).thenReturn(true);

        Assert.assertEquals(1, list.get(1));
        Assert.assertTrue(list.contains(1));

    }

    /**
     * 7 自定义的参数匹配其
     */
    class IsValid implements ArgumentMatcher<Object> {


        @Override
        public boolean matches(Object argument) {
            if (argument instanceof Integer) {
                Integer integer = Integer.valueOf(argument.toString());
                if (integer.equals(1)) {
                    return true;
                }
            }
            System.out.println(argument);
            return false;
        }
    }

    /**
     * 8-自定义参数匹配
     */
    @Test
    public void argumentMatchersTest() {
        //创建mock对象
        List<String> mock = mock(List.class);
        //argThat(Matches<T> matcher)方法用来应用自定义的规则，可以传入任何实现Matcher接口的实现类。
        Mockito.when(mock.addAll(Mockito.argThat(new IsListofTwoElements()))).thenReturn(true);
        Assert.assertTrue(mock.addAll(Arrays.asList("one", "two", "three")));
    }


    class IsListofTwoElements implements ArgumentMatcher<List> {

        @Override
        public boolean matches(List argument) {
            return argument.size() == 3;
        }
    }

    //9预期回调接口生成期望值
    @Test
    public void answerTest() throws Exception {
        List mockList = mock(List.class);
        //使用方法预期回调接口生成期望值（Answer结构）
        Mockito.when(mockList.get(Mockito.anyInt())).thenAnswer(new CustomAnswer());
        Assert.assertEquals("hello world:0", mockList.get(0));
        Assert.assertEquals("hello world:999", mockList.get(999));
    }

    /**
     * 自定义的接口回复类实现 Answer接口
     */
    class CustomAnswer implements Answer<String> {
        @Override
        public String answer(InvocationOnMock invocation) throws Exception {
            Object[] args = invocation.getArguments();
            return "hello world:" + args[0];
        }
    }

    /**
     * 10预期回调接口生成期望值（直接执行）
     */
    @Test
    public void testAnswer1() {
        List<String> mock = Mockito.mock(List.class);
        Mockito.doAnswer(new CustomAnswer2()).when(mock).get(Mockito.anyInt());
        Assert.assertEquals("大于三", mock.get(4));
        Assert.assertEquals("小于三", mock.get(2));
        //TDD  输出预期改变值
        System.out.println(mock.get(2));
    }

    public class CustomAnswer2 implements Answer<String> {
        public String answer(InvocationOnMock invocation) throws Throwable {
            Object[] args = invocation.getArguments();
            Integer num = (Integer) args[0];
            if (num > 3) {
                return "大于三";
            } else {
                return "小于三";
            }
        }
    }

    /**
     * 11 修改对未预设的调用返回默认期望（指定返回值）
     */
    @Test
    public void test13() throws Exception {
        //mock对象使用Answer来对未预设的调用返回默认期望值
        List mock = Mockito.mock(List.class, new Answer() {
            @Override
            public Object answer(InvocationOnMock invocation) throws Throwable {
                return 999;
            }
        });
        //下面的get(1)没有预设，通常情况下会返回NULL，但是使用了Answer改变了默认期望值
        Assert.assertEquals(999, mock.get(1));
        //下面的size()没有预设，通常情况下会返回0，但是使用了Answer改变了默认期望值
        Assert.assertEquals(999, mock.size());
    }

    /**
     * 12 用spy监控真实对象,设置真实对象行为
     */
    @Test(expected = IndexOutOfBoundsException.class)
    public void spy_on_real_objects() {
        List list = new LinkedList();
        List spy = Mockito.spy(list);
        //下面预设的spy.get(0)会报错，因为会调用真实对象的get(0)，所以会抛出越界异常
        //Mockito.when(spy.get(0)).thenReturn(3);

        //使用doReturn-when可以避免when-thenReturn调用真实对象api
        Mockito.doReturn(999).when(spy).get(999);
        //预设size()期望值
        Mockito.when(spy.size()).thenReturn(100);
        //调用真实对象的api
        spy.add(1);
        spy.add(2);
        Assert.assertEquals(100, spy.size());
        Assert.assertEquals(1, spy.get(0));
        Assert.assertEquals(2, spy.get(1));
        Assert.assertEquals(999, spy.get(999));
    }


    /**
     * 13 不做任何返回
     */
    @Test
    public void test14() {
        A a = Mockito.mock(A.class);
        //void 方法才能调用doNothing()
        Mockito.doNothing().when(a).setName(Mockito.anyString());
        a.setName("bb");
        Assert.assertEquals("bb", a.getName());
    }

    class A {
        private String name;

        private void setName(String name) {
            this.name = name;
        }

        private String getName() {
            return name;
        }
    }

    /**
     * 14 调用真实的方法
     */
    @Test
    public void Test() {
        B a = Mockito.mock(B.class);
        //void 方法才能调用doNothing()
        Mockito.when(a.getName()).thenReturn("bb");
        Assert.assertEquals("bb",a.getName());
        //等价于Mockito.when(a.getName()).thenCallRealMethod();
        Mockito.doCallRealMethod().when(a).getName();
        Assert.assertEquals("zhangsan",a.getName());
    }
    class B {
        public String getName(){
            return "zhangsan";
        }
    }

    /**
     * 15 重置mock
     */
    @Test
    public void reset_mock(){
        List list = mock(List.class);
        Mockito. when(list.size()).thenReturn(10);
        list.add(1);
        Assert.assertEquals(10,list.size());
        //重置mock，清除所有的互动和预设
        Mockito.reset(list);
        Assert.assertEquals(0,list.size());
    }
}
